package com.u.social.article.service.impl;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.u.social.article.entity.ArticleIndex;
import com.u.social.article.entity.ArticleTopics;
import com.u.social.article.entity.Articles;
import com.u.social.article.entity.Topics;
import com.u.social.article.mapper.ArticlesMapper;
import com.u.social.article.model.ArticleWithTopicDTO;
import com.u.social.article.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Alex Wu
 * @since 2025-01-23
 */
@Slf4j
@Service
public class ArticlesServiceImpl extends ServiceImpl<ArticlesMapper, Articles> implements IArticlesService {

    @Autowired
    private ITopicsService topicsService;

    @Autowired
    private IArticleTopicsService articleTopicsService;

    @Autowired
    private ArticlesMapper articlesMapper;


    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private IRedisService redisService;

    @Autowired
    private IArticleIndexService articleIndexService;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolExecutor;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public ArticleWithTopicDTO saveWithTopic(ArticleWithTopicDTO articleWithTopicDTO) {
        try {
            List<Topics> topics = articleWithTopicDTO.getTopics();
            Articles article = articleWithTopicDTO.getArticle();
            this.save(article);
            saveArticleTopic(articleWithTopicDTO, topics, article);
            // 缓存帖子对象到 Redis, 60分钟
            cacheArticle(articleWithTopicDTO, 60 * 60L);

            // 缓存帖子收藏数到 Redis
            redisService.putHash("article_collect_count", article.getId().toString(), "0");

            // 缓存帖子评论数到 Redis
            redisService.putHash("article_comment_count", article.getId().toString(), "0");

            // 缓存帖子点赞数到 Redis
            redisService.putHash("article_like_count", article.getId().toString(), "0");

            // 同步到 ES 当中
            ArticleIndex articleIndex = new ArticleIndex();
            articleIndex.setId(article.getId());
            articleIndex.setTitle(article.getTitle());
            articleIndex.setContent(article.getContent());
            articleIndex.setTopics(topics.stream().map(Topics::getTitle).collect(Collectors.toList()));
            articleIndex.setLikeCount(0L);
            // 提交异步任务到线程池，保存数据到 Elasticsearch
            threadPoolExecutor.submit(() -> {
                try {
                    // 异步调用保存文章索引到 Elasticsearch
                    // TODO: 需要将文章添加语义类型，以便于语义搜索，不仅仅是关键字搜索
                    articleIndexService.save(articleIndex);
                } catch (Exception e) {
                    log.error("Failed to save article index to Elasticsearch: {}", e.getMessage());
                }
            });

        } catch (Exception e) {
            log.error("saveWithTopic error: {}", e.getMessage());
            throw new RuntimeException("saveWithTopic error");
        }
        return articleWithTopicDTO;
    }

    private Boolean cacheArticle(ArticleWithTopicDTO articleWithTopicDTO, Long ttlSeconds) {
        if (articleWithTopicDTO != null) {
            Long  articleId= articleWithTopicDTO.getArticle().getId();
            if ( articleId != null) {
                log.info("article save db success");
                // 缓存到 Redis, 60分钟
                redisService.set("article_" + articleId, articleWithTopicDTO, ttlSeconds);
                return true;
            }
        }
        return false;
    }


    @Override
    public Long asyncSaveArticle(ArticleWithTopicDTO articleWithTopicDTO) {
        Long articleId = 0L;
        try {
             articleId = IdUtil.getSnowflakeNextId();
            articleWithTopicDTO.getArticle().setId(articleId);
            rocketMQTemplate.asyncSend("article-topic", JSONObject.toJSONString(articleWithTopicDTO), new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {

                    log.info("Async message sent: " + articleWithTopicDTO);
                }
                @Override
                public void onException(Throwable e) {

                    log.error("Async message error: " + e);
                }
            });

        } catch (Exception e) {
            log.error("saveWithTopic error: {}", e.getMessage());
            throw new RuntimeException("saveWithTopic error");
        }
        return articleId;
    }

    @Override
    public Boolean loadPublicResult(Long id) {
        return redisService.get("article_" + id) != null ;
    }

    private void saveArticleTopic(ArticleWithTopicDTO articleWithTopicDTO, List<Topics> topics, Articles article) {
        Long articleId = article.getId();
        for (Topics topic : topics) {
            Long id = topic.getId();
            if (id == null || id == 0) {
                topicsService.save(topic);
                ArticleTopics articleTopics = new ArticleTopics();
                articleTopics.setArticleId(articleId);
                articleTopics.setTopicId(topic.getId());
                articleTopicsService.save(articleTopics);
            }
        }
        log.info("saveWithTopic success: {}", articleWithTopicDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateWithTopic(ArticleWithTopicDTO articleWithTopicDTO) {
        try {
            List<Topics> topics = articleWithTopicDTO.getTopics();
            Articles article = articleWithTopicDTO.getArticle();
            this.updateById(article);
            articleTopicsService.removeByArticleId(article.getId());
            this.saveArticleTopic(articleWithTopicDTO, topics, article);
        } catch (Exception e) {
            log.error("updateWithTopic error: {}", e.getMessage());
            throw new RuntimeException("updateWithTopic error");
        }
        return true;
    }


    @Override
    public List<ArticleWithTopicDTO> getMineList(Long userId) {
        try {

            List<Articles> articles = articlesMapper.selectMyArticles(123L);
            log.debug("查询我的帖子列表:{}", articles);

        } catch (Exception e) {
            log.error("addArticleWithTopic error: {}", e.getMessage());
            throw new RuntimeException("addArticleWithTopic error");
        }
        return null;
    }

    @Override
    public Boolean softDelete(Long id) {
        try {
            Articles article = new Articles();
            article.setId(id);
            article.setIsDeleted(true);
            this.updateById(article);
            log.info("softDelete success: {}", id);
        } catch (Exception e) {
            log.error("softDelete error: {}", e.getMessage());
            throw new RuntimeException("softDelete error");
        }
        return true;
    }

    @Override
    public List<Articles> findHotArticles(long start, long end) {
        return fetchArticlesByKey("hot_article_ids",start, end);
    }

    @Override
    public List<Articles> findRecommendArticles(long userId, long start, long end) {
        return fetchArticlesByKey(userId + ":recommend_article_ids",start, end);
    }

    private List<Articles> fetchArticlesByKey(String key, long start, long end) {
        List hotArticleIds = redisService.getList(key, start, end);
        if (hotArticleIds != null && hotArticleIds.size() > 0) {
            List<Articles> hotArticles = articlesMapper.selectBatchIds(hotArticleIds);
            return hotArticles;
        }
        return null;
    }

    /**
     * 定时任务，每隔 5 秒执行一次，获取热门文章
     */
    @Scheduled(fixedRate = 5000) // 每隔 5 秒执行一次
    public void fetchHotArticle() {
        List<Articles> hotArticles = articlesMapper.selectList(
                new QueryWrapper<Articles>().eq("is_deleted", 0).orderByDesc("create_time").last("LIMIT 10")
        );
        List<Long> hotArticleIds = hotArticles.stream().map(Articles::getId).collect(Collectors.toList());
        redisService.addList("hot_article_ids", hotArticleIds);

        expandHotArticleTTL(hotArticles);
    }

    /**
     * 定时任务，每隔 5 秒执行一次，获取个人推荐文章
     */
    @Scheduled(fixedRate = 5000) // 每隔 5 秒执行一次
    public void loadRecommendArticle() {
        List<Articles> hotArticles = articlesMapper.selectList(
                new QueryWrapper<Articles>().eq("is_deleted", 0).orderByDesc("update_time").last("LIMIT 10")
        );
        List<Long> hotArticleIds = hotArticles.stream().map(Articles::getId).collect(Collectors.toList());

        redisService.addList("123:recommend_article_ids", hotArticleIds);

        expandHotArticleTTL(hotArticles);
    }

    private void expandHotArticleTTL(List<Articles> hotArticles) {
        for (Articles article : hotArticles) {
            String key = "article_" + article.getId();
            if (!redisService.exists(key)) {
                redisService.set(key, article, 24 * 60 * 60L);
            } else {
                redisService.expire(key, 24 * 60 * 60L);
            }
        }
    }
}
